#include "Client.h"
#include "Block.h"
#include "BlockRender.h"
#include "nbt.h"

void Chunk::_init(){
	render = geCreateRenderer(client->res.shader_base);
//	render->draw_mode = GE_STREAM_DRAW;
	render->draw_mode = GE_STATIC_DRAW;
	render->blend_enabled = true;
	visible = false;
	reserved = false;
	blocks = (Block*)geMalloc(sizeof(Block) * 16 * 16 * 256);
	for(u32 i=0; i<16*16*256; i++){
		blocks[i] = Block();
	}
	lod = 0;
}

Chunk::Chunk(Client* cli){
	client = cli;
	_init();
}

Chunk::Chunk(Client* cli, int x, int y) {
	client = cli;
	X = x;
	Y = y;
	_init();
}

Chunk::~Chunk(){
}

ge_Renderer* Chunk::getRenderer(){
	return render;
}

int Chunk::getLod(){
	return lod;
}

bool Chunk::isVisible(){
	return visible;
}

void Chunk::setVisible(bool vi){
	visible = vi;
}

bool Chunk::isRendered(){
	return rendered;
}

void Chunk::setRendered(bool r){
	rendered = r;
}

int Chunk::getId(){
	return id;
}

void Chunk::setId(int i){
	id = i;
}

int Chunk::getX(){
	return X;
}

int Chunk::getY(){
	return Y;
}

int Chunk::getNewX(){
	return newX;
}

int Chunk::getNewY(){
	return newY;
}

void Chunk::setX(int x){
	X = x;
}

void Chunk::setY(int y){
	Y = y;
}

void Chunk::setNewX(int x){
	newX = x;
}

void Chunk::setNewY(int y){
	newY = y;
}

bool Chunk::isReserved(){
	return reserved;
}

void Chunk::setReserved(bool r){
	reserved = r;
}

Block* Chunk::GetBlock(int x, int y, int z){
	if(x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 256){
		return &blocks[y*16*256 + x*256 + z];
	}
	return NULL;
}

Block* Chunk::FindBlock(int x, int y, int z){
	if(x < 0 || x >= 16 || y < 0 || y >= 16 || z < 0 || z >= 256){
		Block* b = client->getWorld()->FindBlock(X+x, Y+y, z);
		if(b){
			return b;
		}
		return Block::getFakeBlock();
	}
	return &blocks[y*16*256 + x*256 + z];
}

void Chunk::UpdateSign(int x, int y, int z, const char* l1, const char* l2, const char* l3, const char* l4){
	Sign* sign = NULL;
	for(u32 i=0; i<signs.size(); i++){
		if(signs.at(i)->getLocation().getX() == x && signs.at(i)->getLocation().getY() == y && signs.at(i)->getLocation().getZ() == z){
			sign = signs.at(i);
			break;
		}
	}
	if(!sign){
		sign = new Sign(GetBlock(x, y, z), X + x, Y + y, z);
	}
	sign->setLine(0, l1);
	sign->setLine(1, l2);
	sign->setLine(2, l3);
	sign->setLine(3, l4);
	signs.push_back(sign);
}

bool Chunk::Load(int x, int y){
	int rx, ry, cx, cy;
	char rname[128];

	x = (int)(x & 0xFFFFFFF0) / 16;
	y = (int)(y & 0xFFFFFFF0) / 16;

	rx = x >> 5;
	ry = y >> 5;
	cx = x - rx * 32;
	cy = y - ry * 32;

#ifdef ANDROID
	sprintf(rname, "/sdcard/mcclient/world3/region/r.%d.%d.mca", ry, rx);
#else
	sprintf(rname, "world3/region/r.%d.%d.mca", ry, rx);
#endif

	if(!geFileExists(rname)){
		return false;
	}
	ge_File* fp = geFileOpen(rname, GE_FILE_MODE_READ | GE_FILE_MODE_BINARY);
	if(!fp){
		return false;
	}

	u32 header;
//	geFileSeek(fp, sizeof(u32)*(cy*32+cx), GE_FILE_SEEK_SET);
	geFileSeek(fp, sizeof(u32)*(cx*32+cy), GE_FILE_SEEK_SET);
	geFileRead(fp, &header, sizeof(u32));
	header = bswap32(header);
	char usedSectors = header & 0xFF;
	int sectorOffset = header >> 8;

	if(usedSectors <= 0){
		geFileClose(fp);
		return false;
	}
	
	int size;
	char compression;
	geFileSeek(fp, sectorOffset*4096, GE_FILE_SEEK_SET);
	geFileRead(fp, &size, 4);
	geFileRead(fp, &compression, 1);
	size = bswap32(size);

	if(size > 0){
		u8* inbuf = (u8*)geMalloc(usedSectors*4096);
		ge_Buffer* _buf = NULL;
		u8* buf = NULL;
		int ret = geFileRead(fp, inbuf, usedSectors*4096);
		if(compression == 1){
			//Gzip
		}else if(compression == 2){
			_buf = geDecompress(inbuf, usedSectors*4096, GE_COMPRESSION_ZLIB);
			buf = (u8*)_buf->ptr;
			size = _buf->size;
		}
		nbt_node* node = NbtParse(buf, &ret, 0);

		geFree(inbuf);
		if(_buf){
			if(_buf->ptr)geFree(_buf->ptr);
			geFree(_buf);
		}

		nbt_node* sections = NbtFindNode(node, "Sections", true);
		nbt_list* part = sections->payload.list;

	//	printf("sections: 0x%08X\n", (u32)sections);
	//	printf("part: 0x%08X\n", (u32)part);
		memset(this->blocks, 0x0, sizeof(Block)*16*16*256);
		int x, y, z;
		for(y=0; y<16; y++){
			for(x=0; x<16; x++){
				for(z=0; z<256; z++){
					this->blocks[y*16*256 + x*256 + z].setId(0);
					this->blocks[y*16*256 + x*256 + z].setData(0);
					this->blocks[y*16*256 + x*256 + z].setLight(255);
				}
			}
		}
		for(; part != NULL; part=part->next){
			nbt_node* blocks = NbtFindNode(part->node, "Blocks", false);
			nbt_node* lights = NbtFindNode(part->node, "BlockLight", false);
			nbt_node* add = NbtFindNode(part->node, "Add", false);
			nbt_node* data = NbtFindNode(part->node, "Data", false);
			nbt_node* sky = NbtFindNode(part->node, "SkyLight", false);
			nbt_node* Y = NbtFindNode(part->node, "Y", false);
			int Z = 16 * (int)((u8)Y->payload.i8);
			for(y=0; y<16; y++){
				for(x=0; x<16; x++){
					for(z=0; z<16; z++){
						int i = z*16*16 + x*16 + y;
						int j = y*16*256 + x*256 + z+Z;
						this->blocks[j].setId(blocks->payload.byte_array.data[i]);
						if(data){
							if(i % 2 == 0){
								this->blocks[j].setData(data->payload.byte_array.data[i/2] & 0x0F);
							}else{
								this->blocks[j].setData((data->payload.byte_array.data[i/2] & 0xF0) >> 4);
							}
						}
						if(i % 2 == 0){
							this->blocks[j].setLight((lights->payload.byte_array.data[i/2] & 0x0F) * 17);
							this->blocks[j].setLight(this->blocks[j].getLight() + (sky->payload.byte_array.data[i/2] & 0x0F) * 17);
						}else{
							this->blocks[j].setLight(((lights->payload.byte_array.data[i/2] & 0xF0) >> 4) * 17);
							this->blocks[j].setLight(this->blocks[j].getLight() + ((sky->payload.byte_array.data[i/2] & 0xF0) >> 4) * 17);
						}
						this->blocks[j].setLight(0);
					}
				}
			}
		}

		NbtFree(node);
		geFileClose(fp);
		return true;
	}

	geFileClose(fp);
	return false;
}

bool Chunk::LoadSMP(u8* data, int datalen, u16 bitmask, u16 add_bitmask, bool skylight){
	if(!data || datalen <= 0){
		return false;
	}
//	gePrintDebug(0, "Chunk::LoadSMP() size : %d | %d\n", geMemBlockSize(data), datalen);

	int nSections = 0;
	for(int i=0; i<16; i++){
		if((bitmask & (1 << i))){
			nSections++;
		}
	}
	u8* ids = data;
	u8* datas = ids + nSections * 16*16*16;
	u8* lights = datas + nSections * 16*16*8;
	u8* sky = lights + nSections * 16*16*8;
	u8* add = sky + nSections * 16*16*8;

	for(int i=0; i<16; i++){
		if(bitmask & (1 << i)){
			int x, y, z;
			for(y=0; y<16; y++){
				for(x=0; x<16; x++){
					for(z=0; z<16; z++){
						//int ofs = z*16*16 + y*16 + x;
						//int ofs = z*16*16 + y*16 + (16 - x);
						int ofs = z*16*16 + x*16 + y;
						this->blocks[y*16*256 + x*256 + (z + i*16)].setId(ids[ofs]);
						if(ofs % 2 == 0){
							this->blocks[y*16*256 + x*256 + (z + i*16)].setData(datas[ofs / 2] & 0x0F);
							this->blocks[y*16*256 + x*256 + (z + i*16)].setLight(2 * (lights[ofs / 2] & 0x0F));
						}else{
							this->blocks[y*16*256 + x*256 + (z + i*16)].setData((datas[ofs / 2] & 0xF0) >> 4);
							this->blocks[y*16*256 + x*256 + (z + i*16)].setLight(2 * ((lights[ofs / 2] & 0xF0) >> 4));
						}
						//this->blocks[y*16*256 + x*256 + (z + i*16)].setData(0);
						this->blocks[y*16*256 + x*256 + (z + i*16)].setLight(255);
					}
				}
			}
			ids += 16 * 16 * 16;
			datas += 16 * 16 * 8;
			lights += 16 * 16 * 8;
			sky += 16 * 16 * 8;
		}else{
			int x, y, z;
			for(y=0; y<16; y++){
				for(x=0; x<16; x++){
					for(z=0; z<16; z++){
						this->blocks[y*16*256 + x*256 + (z + i*16)].setId(0);
						this->blocks[y*16*256 + x*256 + (z + i*16)].setData(0);
						this->blocks[y*16*256 + x*256 + (z + i*16)].setLight(0);
					}
				}
			}
		}
	}
	return true;
}

void Chunk::LightingRecursion(int x, int y, int z, int n){
	Block* block = GetBlock(x, y, z);
	if(!block){
		block = client->getWorld()->FindBlock(X + x, Y + y, z);
	}
	if(block){
		block->setLight(n * 255 / 15);
	}
}

void Chunk::CalculateLighting(){
	int x, y, z;
	Block* block = blocks;
	Chunk* north = client->getWorld()->FindChunk(X - 16, Y);
	Chunk* south = client->getWorld()->FindChunk(X + 16, Y);
	Chunk* east = client->getWorld()->FindChunk(X, Y - 16);
	Chunk* west = client->getWorld()->FindChunk(X, Y + 16);

	for(y=0; y<16; y++){
		for(x=0; x<16; x++){
			for(z=0; z<256; z++, block++){
				if(block->getId() == 50){
					block->setLight(14 * 255 / 15);
					LightingRecursion(x, y, z, 14);
				}
			}
		}
	}
}

void Chunk::MakeSides(){
	BlockRender brender = BlockRender(client, this);
	Material* material = NULL;
	mc_Vertex* buf[6] = { NULL };
	int bufsz[6] = { 0 };
	int* v = (int*)geMalloc(sizeof(int) * 6);
	int* v0 = (int*)geMalloc(sizeof(int) * 6);
	
	Chunk *n, *s, *e, *w;
	
	Chunk _n = Chunk(client);
	Chunk _s = Chunk(client);
	Chunk _e = Chunk(client);
	Chunk _w = Chunk(client);

	_n.X = this->X - 16;
	_n.Y = this->Y;
	_s.X = this->X + 16;
	_s.Y = this->Y;
	_e.X = this->X;
	_e.Y = this->Y - 16;
	_w.X = this->X;
	_w.Y = this->Y + 16;
	
	n = client->getWorld()->FindChunk(this->X-16, this->Y);
	s = client->getWorld()->FindChunk(this->X+16, this->Y);
	e = client->getWorld()->FindChunk(this->X, this->Y-16);
	w = client->getWorld()->FindChunk(this->X, this->Y+16);
	
	if(!n && client->getWorld()->getGameMode() == SOLO){
		n = &_n;
		if(!n->Load(n->X, n->Y)){
		//	GenerateChunk(n, 0, world->options.seed);
		}
	}
	if(!s && client->getWorld()->getGameMode() == SOLO){
		s = &_s;
		if(!s->Load(s->X, s->Y)){
		//	GenerateChunk(s, 0, world->options.seed);
		}
	}
	if(!e && client->getWorld()->getGameMode() == SOLO){
		e = &_e;
		if(!e->Load(e->X, e->Y)){
		//	GenerateChunk(e, 0, world->options.seed);
		}
	}
	if(!w && client->getWorld()->getGameMode() == SOLO){
		w = &_w;
		if(!w->Load(w->X, w->Y)){
		//	GenerateChunk(w, 0, world->options.seed);
		}
	}
	
	int x, y, z, solid;
	for(solid=1; solid>=0; solid--){
		if(solid == 0){
		//	this->verts_alpha = v; // Unused
		}
		Block* block = blocks;
		for(y=0; y<16; y++){
			for(x=0; x<16; x++){
				for(z=0; z<256; z++, block++){
					//block = FindBlock(x, y, z);
					//block = &blocks[y*16*256 + x*256 + z];
					for(int k=0; k<6; k++){
						if(v[k] >= (bufsz[k]-8)){
							bufsz[k] += 8*4096;
							buf[k] = (mc_Vertex*)geRealloc(buf[k], client->options.vertex_size*bufsz[k]);
						}
						if(v[k] >= (bufsz[k]-8)){
							z = 256;
							x = 16;
							y = 16;
							solid = -1;
							v[k] = bufsz[k];
						//	break;
						}
					}

					if(block->getId() == 0){
						continue;
					}
					float height = 1.0;
					material = Material::FindByIdData(block->getId(), block->getData() & 15);
					if(!material){
						continue;
					}
					if(material->isSolid() != solid){
						continue;
					}
					if(block->getId() == 66){
						height = 0.1;
					}
					if(block->getId() == 78){
						height = (float)(block->getData() + 1) / 8.0;
					}
				
					Block* north = GetBlock(x-1, y, z);
					Block* south = GetBlock(x+1, y, z);
					Block* east = GetBlock(x, y-1, z);
					Block* west = GetBlock(x, y+1, z);
					Block* up = GetBlock(x, y, z+1);
					Block* down = GetBlock(x, y, z-1);
					
					if(!north && n){
						north = n->GetBlock(15, y, z);
					}
					if(!south && s){
						south = s->GetBlock(0, y, z);
					}
					if(!east && e){
						east = e->GetBlock(x, 15, z);
					}
					if(!west && w){
						west = w->GetBlock(x, 0, z);
					}
					
					memcpy(v0, v, sizeof(int) * 6);
					if(brender.Generate(block, north, south, east, west, up, down, buf, &v, x, y, z, height) && this->lod == 1){
						u32 color = Material::GetBlockColor(block->getId(), block->getData());
						for(int k=0; k<6; k++){
							for(int m=v0[k]; m<v[k]; m++){
								if(client->options.opengl_mode == GL_MODE_FAST){
									((mc_Vertex_Fast**)buf)[k][m].color[0] = R(color) * ((mc_Vertex_Fast**)buf)[k][m].color[0] / 255;
									((mc_Vertex_Fast**)buf)[k][m].color[1] = G(color) * ((mc_Vertex_Fast**)buf)[k][m].color[1] / 255;
									((mc_Vertex_Fast**)buf)[k][m].color[2] = B(color) * ((mc_Vertex_Fast**)buf)[k][m].color[2] / 255;
									((mc_Vertex_Fast**)buf)[k][m].color[3] = A(color) * ((mc_Vertex_Fast**)buf)[k][m].color[3] / 255;
								}else
								if(client->options.opengl_mode == GL_MODE_BASIC){
									((mc_Vertex_Basic**)buf)[k][m].color[0] = R(color) * ((mc_Vertex_Basic**)buf)[k][m].color[0] / 255;
									((mc_Vertex_Basic**)buf)[k][m].color[1] = G(color) * ((mc_Vertex_Basic**)buf)[k][m].color[1] / 255;
									((mc_Vertex_Basic**)buf)[k][m].color[2] = B(color) * ((mc_Vertex_Basic**)buf)[k][m].color[2] / 255;
									((mc_Vertex_Basic**)buf)[k][m].color[3] = A(color) * ((mc_Vertex_Basic**)buf)[k][m].color[3] / 255;
								}else
								if(client->options.opengl_mode == GL_MODE_FULL){
									((mc_Vertex**)buf)[k][m].color[0] = R(color) * ((mc_Vertex**)buf)[k][m].color[0] / 255;
									((mc_Vertex**)buf)[k][m].color[1] = G(color) * ((mc_Vertex**)buf)[k][m].color[1] / 255;
									((mc_Vertex**)buf)[k][m].color[2] = B(color) * ((mc_Vertex**)buf)[k][m].color[2] / 255;
									((mc_Vertex**)buf)[k][m].color[3] = A(color) * ((mc_Vertex**)buf)[k][m].color[3] / 255;
								}
							}
						}
					}
				}
			}
		}
	}
	
	geFree(_n.blocks);
	geFree(_s.blocks);
	geFree(_e.blocks);
	geFree(_w.blocks);

	u8* final_verts = (u8*)geMalloc((v[0]+v[1]+v[2]+v[3]+v[4]+v[5]) * client->options.vertex_size);
	memcpy(final_verts, buf[0], v[0] * client->options.vertex_size);
	memcpy(&final_verts[(v[0]) * client->options.vertex_size], buf[1], v[1] * client->options.vertex_size);
	memcpy(&final_verts[(v[0]+v[1]) * client->options.vertex_size], buf[2], v[2] * client->options.vertex_size);
	memcpy(&final_verts[(v[0]+v[1]+v[2]) * client->options.vertex_size], buf[3], v[3] * client->options.vertex_size);
	memcpy(&final_verts[(v[0]+v[1]+v[2]+v[3]) * client->options.vertex_size], buf[4], v[4] * client->options.vertex_size);
	memcpy(&final_verts[(v[0]+v[1]+v[2]+v[3]+v[4]) * client->options.vertex_size], buf[5], v[5] * client->options.vertex_size);

	render->verts = (ge_Vertex*)final_verts;
	render->nVerts = v[0] + v[1] + v[2] + v[3] + v[4] + v[5];
	geFree(v);
	geFree(v0);
	geFree(buf[0]);
	geFree(buf[1]);
	geFree(buf[2]);
	geFree(buf[3]);
	geFree(buf[4]);
	geFree(buf[5]);
}

void Chunk::MakeFarSides(int lod){
	this->lod = lod;
	if(lod == 1){
		MakeSides();
	}else
	if(lod > 1){
		BlockRender brender = BlockRender(client, this, (float)lod);
		mc_Vertex* buf[6] = { NULL };
		int bufsz[6] = { 0 };
		int* v = (int*)geMalloc(sizeof(int) * 6);
		int* v0 = (int*)geMalloc(sizeof(int) * 6);
		int x, y, z;
		for(y=0; y<16; y+=lod){
			for(x=0; x<16; x+=lod){
				for(z=0; z<256; z+=lod){
					for(int k=0; k<6; k++){
						if(v[k] >= (bufsz[k]-8)){
							bufsz[k] += 8*4096;
							buf[k] = (mc_Vertex*)geRealloc(buf[k], client->options.vertex_size*bufsz[k]);
						}
					}
					bool rnorth = false;
					bool rsouth = false;
					bool reast = false;
					bool rwest = false;
					bool rup = false;
					bool rdown = false;
					int i, j, k;
					u32 r = 0;
					u32 g = 0;
					u32 b = 0;
					u32 n = 0;
					for(j=0; j<lod; j++){
						for(i=0; i<lod; i++){
							for(k=0; k<lod; k++){
								Block* b = NULL;
								Material* m = NULL;
								Block* cblock = GetBlock(x+i, y+j, z+k);
								if(cblock && cblock->getMaterial() && cblock->getMaterial()->isSolid()){
									if(i==0 && (b = GetBlock(x+i-1, y+j, z+k)) && (m = b->getMaterial()) && !m->isSolid()){
										rnorth = true;
									}
									if(i==lod-1 && (b = GetBlock(x+i+1, y+j, z+k)) && (m = b->getMaterial()) && !m->isSolid()){
										rsouth = true;
									}
									if(j==0 && (b = GetBlock(x+i, y+j-1, z+k)) && (m = b->getMaterial()) && !m->isSolid()){
										reast = true;
									}
									if(j==lod-1 && (b = GetBlock(x+i, y+j+1, z+k)) && (m = b->getMaterial()) && !m->isSolid()){
										rwest = true;
									}
									if(k==0 && (b = GetBlock(x+i, y+j, z+k-1)) && (m = b->getMaterial()) && !m->isSolid()){
										rdown = true;
									}
									if(k==lod-1 && (b = GetBlock(x+i, y+j, z+k+1)) && (m = b->getMaterial()) && !m->isSolid()){
										rup = true;
									}
									if(cblock){
										u32 c = Material::GetBlockColor(cblock->getId(), cblock->getData());
										r += R(c);
										g += G(c);
										b += B(c);
										n++;
									}
								}
							}
						}
					}
					if(n <= 0){
						n = 1;
					}
					r /= n;
					g /= n;
					b /= n;
					
					memcpy(v0, v, sizeof(int) * 6);
					brender.SetBlock(GetBlock(x, y, z));
					brender.ForceMaterial(Material::FindById(1));
					brender.GenerateCube(lod, rnorth, rsouth, reast, rwest, rup, rdown, (u8**)buf, &v, x, y, z);

					for(int k=0; k<6; k++){
						for(int m=v0[k]; m<v[k]; m++){
								((mc_Vertex_Basic**)buf)[k][m].color[0] = std::max(0U, std::min(255U, r));
								((mc_Vertex_Basic**)buf)[k][m].color[1] = std::max(0U, std::min(255U, g));
								((mc_Vertex_Basic**)buf)[k][m].color[2] = std::max(0U, std::min(255U, b));
								((mc_Vertex_Basic**)buf)[k][m].color[2] = 255;
							/*
							if(client->options.opengl_mode == GL_MODE_FAST){
								((mc_Vertex_Fast**)buf)[k][m].color[0] = r * ((mc_Vertex_Fast**)buf)[k][m].color[0] / 255;
								((mc_Vertex_Fast**)buf)[k][m].color[1] = g * ((mc_Vertex_Fast**)buf)[k][m].color[1] / 255;
								((mc_Vertex_Fast**)buf)[k][m].color[2] = b * ((mc_Vertex_Fast**)buf)[k][m].color[2] / 255;
								//((mc_Vertex_Fast**)buf)[k][m].color[3] = A(color) * ((mc_Vertex_Fast**)buf)[k][m].color[3] / 255;
							}else
							if(client->options.opengl_mode == GL_MODE_BASIC){
								((mc_Vertex_Basic**)buf)[k][m].color[0] = r * ((mc_Vertex_Basic**)buf)[k][m].color[0] / 255;
								((mc_Vertex_Basic**)buf)[k][m].color[1] = g * ((mc_Vertex_Basic**)buf)[k][m].color[1] / 255;
								((mc_Vertex_Basic**)buf)[k][m].color[2] = b * ((mc_Vertex_Basic**)buf)[k][m].color[2] / 255;
								//((mc_Vertex_Basic**)buf)[k][m].color[3] = A(color) * ((mc_Vertex_Basic**)buf)[k][m].color[3] / 255;
							}else
							if(client->options.opengl_mode == GL_MODE_FULL){
								((mc_Vertex**)buf)[k][m].color[0] = r * ((mc_Vertex**)buf)[k][m].color[0] / 255;
								((mc_Vertex**)buf)[k][m].color[1] = g * ((mc_Vertex**)buf)[k][m].color[1] / 255;
								((mc_Vertex**)buf)[k][m].color[2] = b * ((mc_Vertex**)buf)[k][m].color[2] / 255;
								//((mc_Vertex**)buf)[k][m].color[3] = A(color) * ((mc_Vertex**)buf)[k][m].color[3] / 255;
							}
							*/
						}
					}
				}
			}
		}
		u8* final_verts = (u8*)geMalloc((v[0]+v[1]+v[2]+v[3]+v[4]+v[5]) * client->options.vertex_size);
		memcpy(final_verts, buf[0], v[0] * client->options.vertex_size);
		memcpy(&final_verts[(v[0]) * client->options.vertex_size], buf[1], v[1] * client->options.vertex_size);
		memcpy(&final_verts[(v[0]+v[1]) * client->options.vertex_size], buf[2], v[2] * client->options.vertex_size);
		memcpy(&final_verts[(v[0]+v[1]+v[2]) * client->options.vertex_size], buf[3], v[3] * client->options.vertex_size);
		memcpy(&final_verts[(v[0]+v[1]+v[2]+v[3]) * client->options.vertex_size], buf[4], v[4] * client->options.vertex_size);
		memcpy(&final_verts[(v[0]+v[1]+v[2]+v[3]+v[4]) * client->options.vertex_size], buf[5], v[5] * client->options.vertex_size);

		render->verts = (ge_Vertex*)final_verts;
		render->nVerts = v[0] + v[1] + v[2] + v[3] + v[4] + v[5];
		geFree(v);
		geFree(v0);
		geFree(buf[0]);
		geFree(buf[1]);
		geFree(buf[2]);
		geFree(buf[3]);
		geFree(buf[4]);
		geFree(buf[5]);
	}
}
